package com.xiangxiao.rpan.storage.cos;

import com.alibaba.fastjson.JSON;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.model.*;
import com.xiangxiao.rpan.storage.constant.GlobalConsts;
import com.xiangxiao.rpan.storage.core.CacheManager;
import com.xiangxiao.rpan.storage.core.StorageProcessor;
import com.xiangxiao.rpan.storage.core.TransferByCacheManager;
import com.xiangxiao.rpan.storage.dto.StorePathDto;
import com.xiangxiao.rpan.storage.exception.StorageExcetion;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;

/**
 * @ClassName CosStorageProcessor
 * @Auther admin
 * @Email 573768011@qq.com
 * @Date 2024/8/2 17:09
 * 存储方式三--Cos腾讯云对象存储
 */
@Slf4j
@Component(value = "CosStorageProcessor")
@ConditionalOnProperty(prefix = "rpan.storage.processor", name = "type", havingValue = "com.xiangxiao.rpan.storage.cos.CosStorageProcessor")
public class CosStorageProcessor implements StorageProcessor {
  private final Logger logger = LoggerFactory.getLogger(CosStorageProcessor.class);
  private static final String SLASH = "/";
  private static final String COMMON_SEPARATOR = "__,__";

  @Autowired
  @Qualifier(value = "cacheManager")
  private CacheManager cacheManager;
  @Resource(name = "asyncTaskExecutor")
  private ThreadPoolTaskExecutor asyncTaskExecutor;
  @Autowired
  @Qualifier(value = "transferByCacheManager")
  private TransferByCacheManager transferByCacheManager;

  private Map<String, String> uploadIdMap = new HashMap<>();

  private Map<String, List<PartETag>> partETagsMap =  new HashMap<>();
  public String getTimeStamp() {
    Long milliSecond = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
    return milliSecond.toString();
  }

  public URL uploadFile(MultipartFile file, String bucket, String key) throws IOException {
    // 指定要上传的文件，这里使用 流类型，不使用文件类型
    int inputStreamLength = file.getBytes().length;
    InputStream inputStream = file.getInputStream();

    ObjectMetadata objectMetadata = new ObjectMetadata();
    objectMetadata.setContentType(file.getContentType());
    objectMetadata.setContentLength(inputStreamLength);

    // 指定文件上传到 COS 上的路径，即对象键。例如对象键为folder/picture.jpg，则表示将文件 picture.jpg 上传到 folder 路径下
    PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, key, inputStream, objectMetadata);
    PutObjectResult putObjectResult = CosStorageConfig.getCosClient().putObject(putObjectRequest);
    System.out.println(putObjectResult.getRequestId());

    return CosStorageConfig.getURL(bucket, key);
  }

  @Override
  public String store(MultipartFile file, String suffix) throws IOException {
    String key = CosStorageConfig.Tencent_path + getTimeStamp() + suffix;
    String bucketName = CosStorageConfig.Tencent_bucketName;
    return uploadFile(file, bucketName, key).getPath();
  }

  /**
   * 初始化分块上传
   * @param bucketName 桶名
   * @param key        key
   * @return
   */
  private String initiateMultipartUpload(String bucketName, String key) throws Exception {

    InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
    String uploadId = null;
    try {
      InitiateMultipartUploadResult initResult = CosStorageConfig.getCosClient().initiateMultipartUpload(request);
      // 获取uploadid
      uploadId = initResult.getUploadId();
    } catch (CosServiceException e) {
      e.printStackTrace();
      throw new Exception("分块上传失败", e);
    }

    return uploadId;
  }

  /**
   * 转换单片文件为流
   * */
  private String convertSingleChunkFile(MultipartFile file, Integer chunkNumber, Integer totalChunks) throws IOException {
    long blockSize = 0;
    if (chunkNumber < totalChunks) { // 非最后一块分片,那就是固定大小
      blockSize = GlobalConsts.FILE_SPLIT_SIZE;
    } else { // 最后一块分片,大小要进行计算
      blockSize = file.getSize() - GlobalConsts.FILE_SPLIT_SIZE * (chunkNumber - 1);
    }

    return transferByCacheManager.convertMuitlFileToStream(file, chunkNumber, totalChunks, blockSize);
  }

  /**
   * 分块上传
   */
  private void batchUpload(String uploadId, String path, String bucketName,
                                  Integer partNumber, long chunkSize, String key, String identifier) {
    try {
      UploadPartRequest uploadPartRequest = new UploadPartRequest();
      uploadPartRequest.setBucketName(bucketName);
      uploadPartRequest.setKey(key);
      uploadPartRequest.setUploadId(uploadId);

      // 设置分块的数据来源输入流
      File file = new File(path);
      InputStream instream = new FileInputStream(file);
      uploadPartRequest.setInputStream(instream);

      // 设置分块的长度
      uploadPartRequest.setPartSize(chunkSize); // 设置数据长度
      uploadPartRequest.setPartNumber(partNumber);     // 假设要上传的part编号是10

      UploadPartResult uploadPartResult = CosStorageConfig.getCosClient().uploadPart(uploadPartRequest);
      PartETag partETag = uploadPartResult.getPartETag();

      List<PartETag> tags = partETagsMap.get(identifier);
      tags.add(partETag);
      partETagsMap.put(identifier, tags);
      file.delete();
    } catch (CosServiceException e) {
      e.printStackTrace();
    } catch (CosClientException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  @Override
  public String storeWithChunk(MultipartFile file, String identifier, Integer totalChunks,
                               Integer chunkNumber, Long totalSize, Long chunkSize, String redisFileChunkKey) throws IOException {
    String bucketName = CosStorageConfig.Tencent_bucketName;
    String key = CosStorageConfig.Tencent_path + "/" + file.getOriginalFilename();

    Future<String> future = asyncTaskExecutor.submit(() -> convertSingleChunkFile(file, chunkNumber, totalChunks));
    try {
      String tempPath = future.get();

      if (chunkNumber == 1) {
        String id = initiateMultipartUpload(bucketName, key);
        uploadIdMap.put(identifier, id);
        partETagsMap.put(identifier, new ArrayList<>());
      }

      batchUpload(uploadIdMap.get(identifier), tempPath, bucketName, chunkNumber, chunkSize, key, identifier);
      return key;
    } catch (Exception e) {
      e.printStackTrace();
      throw new StorageExcetion("文件分片上传失败");
    }
  }

  @Override
  public String mergeChunks(String md5, int chunkCount, String suffix) throws IOException {
    String bucketName = CosStorageConfig.Tencent_bucketName;
    String cacheChunkKey = GlobalConsts.FILE_CHUNK_KEY + ":" + md5;
    StorePathDto storePath = JSON.parseObject(String.valueOf(cacheManager.get(cacheChunkKey)), StorePathDto.class);

    try {
      CompleteMultipartUploadRequest completeMultipartUploadRequest =
          new CompleteMultipartUploadRequest(bucketName, storePath.getPath(), uploadIdMap.get(md5), partETagsMap.get(md5));
      CompleteMultipartUploadResult completeMultipartUploadResult = CosStorageConfig.getCosClient().completeMultipartUpload(completeMultipartUploadRequest);
      System.out.println(completeMultipartUploadResult.getKey());
      return CosStorageConfig.Tencent_printUrl + completeMultipartUploadResult.getKey();
    } catch (CosServiceException e) {
      e.printStackTrace();
      throw new StorageExcetion("文件合并失败");
    }
  }

  @Override
  public String download(String absoluteUrl, HttpServletResponse response) throws IOException {
    return null;
  }

  @Override
  public String downloadWithChunk(byte[] bytes, HttpServletResponse response) throws IOException {
    return null;
  }
}